<template><div><h2 id="版本化" tabindex="-1"><a class="header-anchor" href="#版本化"><span>版本化</span></a></h2>
<p>一个优秀的 API 应该是支持 <em>版本化的</em>，即更改和新特性在新版本的 API 中实现，而不是不断地在同一个版本上修改。</p>
<p>与 Web 应用程序不同，您可以完全控制客户端和服务器端代码，API 意味着由超出您控制范围的客户端使用。出于这个原因，应该尽可能维护 API 的向后兼容性（BC）。如果有必要进行可能破坏 BC 的更改，则应该在 API 的新版本中引入它，并提高版本号。现有的客户端可以继续使用旧的、可用的 API 版本，新的或升级的客户端可以获得新 API 版本中的新功能。</p>
<blockquote>
<p>小贴士：有关设计 API 版本号的更多信息，请参阅 <a href="https://semver.org/lang/zh-CN/" title="语义化版本" target="_blank" rel="noopener noreferrer">语义化版本</a>。</p>
</blockquote>
<h2 id="常用方法" tabindex="-1"><a class="header-anchor" href="#常用方法"><span>常用方法</span></a></h2>
<p>实现 API 版本控制的一种常见方法是将版本号嵌入到 API 的 URL 中。例如：<code v-pre>http://example.com/v1/users</code> 表示 API 版本 1 的 <code v-pre>/users</code> 端点（endpoint）。</p>
<p>API 版本控制的另一种方法是将版本号放在 HTTP 请求头中，这种方法最近得到了发展。这通常是通过 <code v-pre>Accept</code> 头文件完成的，例如：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token comment">// 通过一个参数</span></span>
<span class="line">Accept<span class="token punctuation">:</span> application<span class="token operator">/</span>json<span class="token punctuation">;</span> version<span class="token operator">=</span>v1</span>
<span class="line"><span class="token comment">// 通过供应商内容类型</span></span>
<span class="line">Accept<span class="token punctuation">:</span> application<span class="token operator">/</span>vnd<span class="token operator">.</span>company<span class="token operator">.</span>myapp<span class="token operator">-</span>v1<span class="token operator">+</span>json</span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这两种方法都有各自的优点和缺点，对于每种方法都有很多争论。下面您将看到一种实用的 API 版本控制策略，它混合了这两种方法：</p>
<ul>
<li>将 API 实现的每个主要版本放在一个单独的模块中，其 ID 为主要版本号(例如：<code v-pre>v1</code>、<code v-pre>v2</code>等) ，自然地，API 的 URL 中将包含主要的版本号。</li>
<li>在每个主版本中（以及相应的模块中），使用 <code v-pre>Accept</code> HTTP 请求头来确定次要版本号，并相应地编写条件代码来响应次要版本。</li>
</ul>
<h2 id="版本化模块" tabindex="-1"><a class="header-anchor" href="#版本化模块"><span>版本化模块</span></a></h2>
<p>对于服务于主版本的每个模块来说，它应该包含服务于该特定版本的资源和控制器类。为了更好地分离代码责任，您可以保留一组通用的基础资源和控制器类，并在每个单独的版本模块中对它们进行子类化。在子类中，实现具体的代码，如覆盖 <code v-pre>Model::fields()</code>。</p>
<h3 id="创建版本化模块" tabindex="-1"><a class="header-anchor" href="#创建版本化模块"><span>创建版本化模块</span></a></h3>
<p>我们可以通过 Gii 的 module 构造器来实现基本的目录结构，记得取消控制器和视图创建，具体如图：</p>
<p><img src="https://cdn.learnku.com/uploads/images/202203/05/93088/uIPS3mtsMS.png!large" alt="Gii 创建版本化模块"></p>
<p>或者通过命令行：</p>
<div class="language-bash line-numbers-mode" data-highlighter="prismjs" data-ext="sh" data-title="sh"><pre v-pre class="language-bash"><code><span class="line">php yii gii/module <span class="token parameter variable">--moduleID</span><span class="token operator">=</span>v1 <span class="token parameter variable">--moduleClass</span><span class="token operator">=</span><span class="token string">'api\modules\v1\V1Module'</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>然后自行删除创建的 DefaultController 和 views 目录。然后在应用配置中，这样写：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token operator">...</span><span class="token operator">...</span></span>
<span class="line">    <span class="token string single-quoted-string">'modules'</span> <span class="token operator">=></span> <span class="token punctuation">[</span></span>
<span class="line">        <span class="token string single-quoted-string">'v1'</span> <span class="token operator">=></span> <span class="token punctuation">[</span></span>
<span class="line">            <span class="token string single-quoted-string">'class'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'api\modules\v1\V1Module'</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token punctuation">]</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token punctuation">]</span><span class="token punctuation">,</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="创建控制器" tabindex="-1"><a class="header-anchor" href="#创建控制器"><span>创建控制器</span></a></h3>
<p>然后查看 api\modules\v1 目录下，如果没有一个 controllers 目录，那就新建一个，用来放控制器。你可以使用命令行来创建一个 rest 控制器，示例如下：</p>
<div class="language-bash line-numbers-mode" data-highlighter="prismjs" data-ext="sh" data-title="sh"><pre v-pre class="language-bash"><code><span class="line">php yii gii/controller <span class="token parameter variable">--controllerClass</span><span class="token operator">=</span><span class="token string">'api\modules\v1\controllers\UserController'</span> <span class="token parameter variable">--baseClass</span><span class="token operator">=</span><span class="token string">'yii\rest\ActiveController'</span> <span class="token parameter variable">--cations</span><span class="token operator">=</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>你也可以用 gii 来创建控制器，示例如下：</p>
<p><img src="https://cdn.learnku.com/uploads/images/202203/05/93088/ROctV8Is69.png!large" alt="Gii 创建 rest 控制器"></p>
<h3 id="创建表" tabindex="-1"><a class="header-anchor" href="#创建表"><span>创建表</span></a></h3>
<p>如果你还没有创建 user 表，没有关系，我们的 Yii 的高级应用模板，自带了示例的 user 表的迁移文件，你可以执行控制台命令，快速创建 user 表，具体如下：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line">php yii migrate<span class="token operator">/</span>up</span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>如图所示，这样就创建了 user 表：</p>
<p><img src="https://cdn.learnku.com/uploads/images/202203/06/93088/Dj39wbbUod.png!large" alt="数据库迁移创建表"></p>
<h3 id="创建模型" tabindex="-1"><a class="header-anchor" href="#创建模型"><span>创建模型</span></a></h3>
<p>有了 user 表之后，我们在 api\modules\v1\models 目录下存放模型，当然你也可以放在其他或者公共的目录，你可以使用命令行来创建一个模型，示例如下：</p>
<div class="language-bash line-numbers-mode" data-highlighter="prismjs" data-ext="sh" data-title="sh"><pre v-pre class="language-bash"><code><span class="line">php yii gii/model <span class="token parameter variable">--tableName</span><span class="token operator">=</span><span class="token string">'demo_user'</span> <span class="token parameter variable">--ns</span><span class="token operator">=</span><span class="token string">'api\modules\v1\models'</span> <span class="token parameter variable">--modelClass</span><span class="token operator">=</span><span class="token string">'User'</span> <span class="token parameter variable">--generateLabelsFromComments</span><span class="token operator">=</span><span class="token number">1</span> <span class="token parameter variable">--useTablePrefix</span><span class="token operator">=</span><span class="token number">1</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>你也可以用 gii 来创建模型，示例如下：</p>
<p><img src="https://cdn.learnku.com/uploads/images/202203/06/93088/7B5lhO0TtW.png!large" alt="Gii 创建 rest 模型"></p>
<p>然后在之前创建的 <code v-pre>api\modules\v1\controllers\UserController</code> 中，增加一句，示例如下：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">namespace</span> <span class="token package">api<span class="token punctuation">\</span>modules<span class="token punctuation">\</span>v1<span class="token punctuation">\</span>controllers</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">class</span> <span class="token class-name-definition class-name">UserController</span> <span class="token keyword">extends</span> <span class="token class-name class-name-fully-qualified"><span class="token punctuation">\</span>yii<span class="token punctuation">\</span>rest<span class="token punctuation">\</span>ActiveController</span></span>
<span class="line"><span class="token punctuation">{</span></span>
<span class="line">    <span class="token keyword">public</span> <span class="token variable">$modelClass</span> <span class="token operator">=</span> <span class="token string single-quoted-string">'api\modules\v1\models\User'</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="目录结构" tabindex="-1"><a class="header-anchor" href="#目录结构"><span>目录结构</span></a></h3>
<p>经过控制器和模型的创建，您的项目目录，示例如下：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line">api<span class="token operator">/</span></span>
<span class="line">    common<span class="token operator">/</span></span>
<span class="line">        controllers<span class="token operator">/</span></span>
<span class="line">            UserController<span class="token operator">.</span>php</span>
<span class="line">            PostController<span class="token operator">.</span>php</span>
<span class="line">        models<span class="token operator">/</span></span>
<span class="line">            User<span class="token operator">.</span>php</span>
<span class="line">            Post<span class="token operator">.</span>php</span>
<span class="line">    modules<span class="token operator">/</span></span>
<span class="line">        v1<span class="token operator">/</span></span>
<span class="line">            controllers<span class="token operator">/</span></span>
<span class="line">                UserController<span class="token operator">.</span>php</span>
<span class="line">                PostController<span class="token operator">.</span>php</span>
<span class="line">            models<span class="token operator">/</span></span>
<span class="line">                User<span class="token operator">.</span>php</span>
<span class="line">                Post<span class="token operator">.</span>php</span>
<span class="line">            V1Module<span class="token operator">.</span>php</span>
<span class="line">        v2<span class="token operator">/</span></span>
<span class="line">            controllers<span class="token operator">/</span></span>
<span class="line">                UserController<span class="token operator">.</span>php</span>
<span class="line">                PostController<span class="token operator">.</span>php</span>
<span class="line">            models<span class="token operator">/</span></span>
<span class="line">                User<span class="token operator">.</span>php</span>
<span class="line">                Post<span class="token operator">.</span>php</span>
<span class="line">            V2Module<span class="token operator">.</span>php</span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="应用程序配置" tabindex="-1"><a class="header-anchor" href="#应用程序配置"><span>应用程序配置</span></a></h3>
<p>您的应用程序配置如下所示：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">return</span> <span class="token punctuation">[</span></span>
<span class="line">    <span class="token string single-quoted-string">'modules'</span> <span class="token operator">=></span> <span class="token punctuation">[</span></span>
<span class="line">        <span class="token string single-quoted-string">'v1'</span> <span class="token operator">=></span> <span class="token punctuation">[</span></span>
<span class="line">            <span class="token string single-quoted-string">'class'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'app\modules\v1\Module'</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token punctuation">]</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token string single-quoted-string">'v2'</span> <span class="token operator">=></span> <span class="token punctuation">[</span></span>
<span class="line">            <span class="token string single-quoted-string">'class'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'app\modules\v2\Module'</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token punctuation">]</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token punctuation">]</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token string single-quoted-string">'components'</span> <span class="token operator">=></span> <span class="token punctuation">[</span></span>
<span class="line">        <span class="token string single-quoted-string">'urlManager'</span> <span class="token operator">=></span> <span class="token punctuation">[</span></span>
<span class="line">            <span class="token string single-quoted-string">'enablePrettyUrl'</span> <span class="token operator">=></span> <span class="token constant boolean">true</span><span class="token punctuation">,</span></span>
<span class="line">            <span class="token string single-quoted-string">'enableStrictParsing'</span> <span class="token operator">=></span> <span class="token constant boolean">true</span><span class="token punctuation">,</span></span>
<span class="line">            <span class="token string single-quoted-string">'showScriptName'</span> <span class="token operator">=></span> <span class="token constant boolean">false</span><span class="token punctuation">,</span></span>
<span class="line">            <span class="token string single-quoted-string">'rules'</span> <span class="token operator">=></span> <span class="token punctuation">[</span></span>
<span class="line">                <span class="token punctuation">[</span><span class="token string single-quoted-string">'class'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'yii\rest\UrlRule'</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">'controller'</span> <span class="token operator">=></span> <span class="token punctuation">[</span><span class="token string single-quoted-string">'v1/user'</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">'v1/post'</span><span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">,</span></span>
<span class="line">                <span class="token punctuation">[</span><span class="token string single-quoted-string">'class'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'yii\rest\UrlRule'</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">'controller'</span> <span class="token operator">=></span> <span class="token punctuation">[</span><span class="token string single-quoted-string">'v2/user'</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">'v2/post'</span><span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">,</span></span>
<span class="line">            <span class="token punctuation">]</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token punctuation">]</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token punctuation">]</span><span class="token punctuation">,</span></span>
<span class="line"><span class="token punctuation">]</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>综上所述，<code v-pre>http://example.com/v1/users</code> 将返回版本 1 中的用户列表，而 <code v-pre>http://example.com/v2/users</code> 将返回版本 2 的用户。</p>
<p>不同主要版本的代码可以很好地隔离，都归功于 Yii 的 模块（modules）。但是，模块仍然可以通过公共基类及其他共享资源，实现跨模块代码重用。</p>
<h2 id="次要版本号" tabindex="-1"><a class="header-anchor" href="#次要版本号"><span>次要版本号</span></a></h2>
<p>要处理次要版本号，可以利用内容协商特性，它由 <code v-pre>yii\filters\ContentNegotiator</code> 内容协商行为来提供。当决定支持哪种内容类型时，该行为会设置 <code v-pre>yii\web\Response::acceptParams</code> 属性。</p>
<p>例如，如果一个请求被发送带有 HTTP 报头 <code v-pre>Accept: application/json; version=v1</code>，经过内容协商后，<code v-pre>yii\web\Response::acceptParams</code> 将包含值 <code v-pre>['version' =&gt; 'v1']</code>。</p>
<p>根据 <code v-pre>acceptParams</code> 中的版本信息，你可以在动作、资源类、序列化器等地方编写条件代码，以此来提供适当的功能。</p>
<p>由于次要版本的定义要求维护向后兼容性，所以，希望在代码中不会有太多的版本验证。否则，您可能需要创建一个新的主版本。</p>
<blockquote>
<p>💖喜欢本文档的，欢迎点赞、收藏、留言或转发，谢谢支持！<br>
作者邮箱：zhuzixian520@126.com，github地址：<a href="https://github.com/zhuzixian520" target="_blank" rel="noopener noreferrer">github.com/zhuzixian520</a></p>
</blockquote>
</div></template>


